import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.Timer;
import java.util.Vector;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import streamdef.StreamDefinitionParser;
import main.CEPLFunctions;
import main.EventCallbackFunctionOwner;

@SuppressWarnings("serial")
public class Main extends JFrame implements ActionListener,
		EventCallbackFunctionOwner {
	private static boolean usePostgre = false;
	private static String mySQLDBURL = "jdbc:mysql://localhost:3306/CEPL";
	private static String mySQLDBUser = "root";
	private static String mySQLDBPassword = "cepl";
	private static String postgreDBURL = "jdbc:postgresql://localhost:5432/CEPL";
	private static String postgreDBUser = "cepl";
	private static String postgreDBPassword = "cepl";

	public static void main(String[] args) {
		try {
			Main frame = new Main();
			frame.setSize(225, 425);
			frame.setPreferredSize(new Dimension(225, 425));
			frame.setResizable(false);
			frame.createGUI();
			frame.setVisible(true);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
	}

	private Container window;
	private GridLayout gridLayout;

	private JLabel labelForInfo;
	private JLabel labelForStats;
	private JLabel labelForMessages;
	private JButton startButton;
	private JButton stopButton;

	private static int statisticsUpdateIntervalInMilliSeconds = 1000;
	private Timer statisticsUpdatingTimer = null;
	private long nbGeneratedComplexEvents = 0;

	protected void finalize() {
		stopServer();
	}

	private void createGUI() throws UnknownHostException {
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		window = this.getContentPane();
		gridLayout = new GridLayout(5, 1);
		gridLayout.setVgap(0);
		window.setLayout(gridLayout);

		labelForInfo = new JLabel();
		String textInfo = "<html>&nbsp;:: Server Information ::<br>&nbsp;&nbsp;&nbsp;&nbsp;  IP: "
				+ java.net.InetAddress.getLocalHost().getHostAddress()
						.toString()
				+ "<br>&nbsp;&nbsp;&nbsp;&nbsp;  Port: "
				+ UDPServer.PORT + "</html";
		labelForInfo.setText(textInfo);
		window.add(labelForInfo);

		labelForStats = new JLabel();
		labelForStats
				.setText("<html>&nbsp;:: Server Statistics ::<br>&nbsp;&nbsp;&nbsp;&nbsp;  No statistics yet"
						+ "</html");
		window.add(labelForStats);

		startButton = new JButton("Start Server");
		startButton.addActionListener(this);
		startButton.setActionCommand("start");
		window.add(startButton);

		stopButton = new JButton("Stop Server");
		stopButton.addActionListener(this);
		stopButton.setActionCommand("stop");
		stopButton.setEnabled(false);
		window.add(stopButton);

		labelForMessages = new JLabel();
		labelForMessages
				.setText("<html>&nbsp;:: Server Output ::<br>&nbsp;&nbsp;&nbsp;&nbsp;  No messages yet"
						+ "</html");
		window.add(labelForMessages);
	}

	public void actionPerformed(ActionEvent e) {
		if (e.getActionCommand().compareTo("start") == 0)
			startServer();
		else if (e.getActionCommand().compareTo("stop") == 0)
			stopServer();
	}

	private void startServer() {
		// start the cep library
		if (usePostgre == false)
			CEPLFunctions.Main.init(usePostgre, !usePostgre, mySQLDBURL,
					mySQLDBUser, mySQLDBPassword);
		else
			CEPLFunctions.Main.init(usePostgre, !usePostgre, postgreDBURL,
					postgreDBUser, postgreDBPassword);

		// create event formats and queries
		try {
			// message for location updates
			String primQuery1 = "create pstream(1,IP:text:0,name:text:0,location:text:0,timestamp:long)";
			StreamDefinitionParser.instance().parse(primQuery1);

			// message for 'like'-messages
			String primQuery2 = "create pstream(2,IP:text:0,name:text:0,rating:float,timestamp:long)";
			StreamDefinitionParser.instance().parse(primQuery2);

			// connect a received rating to the correct location
			String complexQuery1 = "create cstream(3,IP:1.IP,name:1.name,location:1.location,rating:2.rating,timestamp:2.timestamp)\n";
			complexQuery1 += "from 1:1[3600.0],2:2[3600.0],3:1[!3600.0]\n";
			complexQuery1 += "where (1.IP == 2.IP) and (1.name == 2.name) and (1 BEFORE 2)\n";
			complexQuery1 += "and (1.IP == 3.IP) and (1.name == 3.name) AND (1 BEFORE 3)\n";
			StreamDefinitionParser.instance().parse(complexQuery1);
			// CEPLFunctions.EventCallbacks.addEventCallback(3, this);

			// gather ratings for 'Dance Hall' of the users that are still there
			String complexQuery2 = "create cstream(4,IP:1.IP,name:1.name,location:1.location,rating:1.rating,timestamp:1.timestamp)\n";
			complexQuery2 += "from 1:3[3600.0],2:3[!3600.0],3:2[3600]\n";
			complexQuery2 += "where 1 BEFORE 3\n";
			complexQuery2 += "and 1.location == 'Dance Hall'\n";
			complexQuery2 += "and (1.name == 2.name) and (2.location == 'Dance Hall') and (1 BEFORE 2)\n";
			StreamDefinitionParser.instance().parse(complexQuery2);
			// CEPLFunctions.EventCallbacks.addEventVectorCallback(4, this);

			// gather all the recent locations of the same user
			String complexQuery3 = "create cstream(5,IP:1.IP,name:1.name,location:1.location, timestamp:1.timestamp)\n";
			complexQuery3 += "from 1:1[3600.0],2:1[3600.0]\n";
			complexQuery3 += "where (1.IP == 2.IP) and (1.name == 2.name) and (1 BEFORE 2)\n";
			StreamDefinitionParser.instance().parse(complexQuery3);
			CEPLFunctions.EventCallbacks.addEventVectorCallback(5, this);

			CEPLFunctions.Main.start();
		} catch (ParseException e) {
			e.printStackTrace();
		}

		// start the udp server
		UDPServer.instance().start(labelForStats);

		// statistics subthread
		if (statisticsUpdatingTimer == null) {
			statisticsUpdatingTimer = new Timer();
			statisticsUpdatingTimer.schedule(new StatisticsUpdater(
					statisticsUpdateIntervalInMilliSeconds), 0,
					statisticsUpdateIntervalInMilliSeconds);
		}

		nbGeneratedComplexEvents = 0;

		startButton.setEnabled(false);
		stopButton.setEnabled(true);
	}

	private void stopServer() {
		UDPServer.instance().stop();
		CEPLFunctions.Main.reset();

		startButton.setEnabled(true);
		stopButton.setEnabled(false);

		labelForStats
				.setText("<html>&nbsp;:: Server Statistics ::<br>&nbsp;&nbsp;&nbsp;&nbsp;  No statistics yet"
						+ "</html");
		labelForMessages
				.setText("<html>&nbsp;:: Server Output ::<br>&nbsp;&nbsp;&nbsp;&nbsp;  No messages yet"
						+ "</html");
	}

	private void callFunctionForNewComplexEvent(boolean generateOutput,
			boolean useErr, long streamID, long eventID, Timestamp timestamp,
			byte[] eventData) {
		if (generateOutput) {
			ByteBuffer b = ByteBuffer.wrap(eventData);
			// get the IP
			short ipLength = b.getShort();
			byte[] temp = new byte[ipLength];
			b.get(temp);
			String IP = new String(temp);

			// get the name
			short nameLength = b.getShort();
			byte[] tempName = new byte[nameLength];
			b.get(tempName);
			String name = new String(tempName);

			// get the location
			short locLength = b.getShort();
			byte[] temp2 = new byte[locLength];
			b.get(temp2);
			String location = new String(temp2);

			// rating
			float rating = 0;
			if (streamID != 5)
				try {
					rating = b.getFloat();
				} catch (Exception e) {
					rating = 0;
				}

			// timestamp
			long timestamp_ = b.getLong();
			Timestamp ts = new Timestamp(timestamp_);

			String text = "<html>&nbsp;:: Server Output ::<br>&nbsp;&nbsp;&nbsp;&nbsp;  IP: "
					+ IP
					+ "<br>&nbsp;&nbsp;&nbsp;&nbsp;  Name: "
					+ name
					+ "<br>&nbsp;&nbsp;&nbsp;&nbsp;  Location: "
					+ location
					+ "<br>&nbsp;&nbsp;&nbsp;&nbsp;  Rating: "
					+ rating
					+ "</html";
			labelForMessages.setText(text);

			String text2 = "  " + IP + " :: " + name + " :: " + location
					+ " :: " + rating + " :: " + eventID + " -> " + ts;
			if (useErr == false)
				System.out.println("CALLBACK: " + text2);
			else
				System.err.println("CALLBACK: " + text2);
		} else {
			nbGeneratedComplexEvents++;
			String text = "<html>&nbsp;:: Server Output ::<br>&nbsp;&nbsp;&nbsp;&nbsp;  #callback function calls: "
					+ nbGeneratedComplexEvents + "</html";
			labelForMessages.setText(text);
		}
	}

	@Override
	public void callFunctionForNewComplexEvent(OutgoingComplexEventInfo event) {
		callFunctionForNewComplexEvent(true, false, event.streamID,
				event.eventID, event.timestamp, event.eventData);

	}

	@Override
	public void callFunctionForVectorOfNewComplexEvents(
			Vector<OutgoingComplexEventInfo> events) {
		System.err.println("----------");
		for (int i = 0; i < events.size(); ++i) {
			OutgoingComplexEventInfo event = events.get(i);
			callFunctionForNewComplexEvent(true, true, event.streamID,
					event.eventID, event.timestamp, event.eventData);
		}
		System.err.println("----------");
	}
}
